<script setup lang="ts">
  import { computed, ref, watchEffect } from 'vue'
  import {
    MaybeComputedElementRef,
    useParentElement,
    useElementBounding,
    useScroll,
    MaybeRefOrGetter
  } from '@vueuse/core'
  import { CommonUtil, ArrUtil, FunUtil } from '@xtail/core'
  import { ScrollDirectionConst, ElVisibilityStatusConst } from '../../common'
  import { VisibilityDetailPropsType } from './types'

  defineOptions({
    name: 'SVisibilityDetail'
  })

  const props = withDefaults(defineProps<VisibilityDetailPropsType>(), {
    useScrollOptions: {} as any,
    beforeShow: () => {},
    beforeShowLimit: -1,
    beforeShowLimitExclude: () => false,
    beforeShowLimitOptions: {} as any,
    showed: () => {},
    showedLimit: -1,
    showedLimitExclude: () => false,
    showedLimitOptions: {} as any,
    beforeHide: () => {},
    beforeHideLimit: -1,
    beforeHideLimitExclude: () => false,
    beforeHideLimitOptions: {} as any,
    hidden: () => {},
    hiddenLimit: -1,
    hiddenLimitExclude: () => false,
    hiddenLimitOptions: {} as any,
    directionNotScroll: ScrollDirectionConst.Y,
    beforeShowNotScroll: () => {},
    beforeShowNotScrollLimit: -1,
    beforeShowNotScrollLimitExclude: () => false,
    beforeShowNotScrollLimitOptions: {} as any,
    showedNotScroll: () => {},
    showedNotScrollLimit: -1,
    showedNotScrollLimitExclude: () => false,
    showedNotScrollLimitOptions: {} as any,
    hiddenNotScroll: () => {},
    hiddenNotScrollLimit: -1,
    hiddenNotScrollLimitExclude: () => false,
    hiddenNotScrollLimitOptions: {} as any
  })

  /**
   * 对组件参数 margin 的预处理
   */
  const containerMargin = computed(() => {
    if (!props.margin || props.margin.length === 0) return [0, 0, 0, 0]
    else {
      const marginArr = props.margin as number[]
      return CommonUtil.isAnyEq(
        props.margin.length,
        [
          [1, () => ArrUtil.repeat(marginArr, 4)],
          [2, () => ArrUtil.repeat(marginArr, 2)],
          [3, () => [...marginArr, marginArr[1]]]
        ],
        () => marginArr
      )
    }
  })

  /**
   * 根据组件参数传递的配置对回调函数进行限制调用次数的处理
   */
  const beforeShowHandler = computed(() =>
    FunUtil.limitCall(props.beforeShow, {
      maxCount: props.beforeShowLimit,
      exclude: () => props.beforeShowLimitExclude(ElVisibilityStatusConst.BEFORE_SHOW),
      ...props.beforeShowLimitOptions
    })
  )
  const showedHandler = computed(() =>
    FunUtil.limitCall(props.showed, {
      maxCount: props.showedLimit,
      exclude: () => props.showedLimitExclude(ElVisibilityStatusConst.SHOWED),
      ...props.showedLimitOptions
    })
  )
  const beforeHideHandler = computed(() =>
    FunUtil.limitCall(props.beforeHide, {
      maxCount: props.beforeHideLimit,
      exclude: () => props.beforeHideLimitExclude(ElVisibilityStatusConst.BEFORE_HIDE),
      ...props.beforeHideLimitOptions
    })
  )
  const hiddenHandler = computed(() =>
    FunUtil.limitCall(props.hidden, {
      maxCount: props.hiddenLimit,
      exclude: () => props.hiddenLimitExclude(ElVisibilityStatusConst.HIDDEN),
      ...props.hiddenLimitOptions
    })
  )
  const beforeShowNotScrollHandler = computed(() =>
    FunUtil.limitCall(props.beforeShowNotScroll, {
      maxCount: props.beforeShowNotScrollLimit,
      exclude: () =>
        props.beforeShowNotScrollLimitExclude(ElVisibilityStatusConst.BEFORE_SHOW_NOT_SCROLL),
      ...props.beforeShowNotScrollLimitOptions
    })
  )
  const showedNotScrollHandler = computed(() =>
    FunUtil.limitCall(props.showedNotScroll, {
      maxCount: props.showedNotScrollLimit,
      exclude: () => props.showedNotScrollLimitExclude(ElVisibilityStatusConst.SHOWED_NOT_SCROLL),
      ...props.showedNotScrollLimitOptions
    })
  )
  const hiddenNotScrollHandler = computed(() =>
    FunUtil.limitCall(props.hiddenNotScroll, {
      maxCount: props.hiddenNotScrollLimit,
      exclude: () => props.hiddenNotScrollLimitExclude(ElVisibilityStatusConst.HIDDEN_NOT_SCROLL),
      ...props.hiddenNotScrollLimitOptions
    })
  )

  const compRootElRef = ref<HTMLDivElement>()
  const parentElRef = useParentElement(compRootElRef)
  const containerElRef = computed(() => (props.container ? props.container : parentElRef.value))

  const compRootElBounding = useElementBounding(compRootElRef)
  const containerElBounding = useElementBounding(containerElRef as MaybeComputedElementRef)

  const containerScroll = useScroll(containerElRef as MaybeRefOrGetter, props.useScrollOptions)

  /**
   * 计算扩大或缩小浏览器视口后的区域范围
   */
  const containerTop = computed(() => containerElBounding.top.value + containerMargin.value[1])
  const containerBottom = computed(
    () => containerElBounding.bottom.value + containerMargin.value[3]
  )
  const containerRight = computed(() => containerElBounding.right.value + containerMargin.value[2])
  const containerLeft = computed(() => containerElBounding.left.value + containerMargin.value[4])

  // 容器滚动条在垂直方向上滚动时，组件是否在容器顶部边界上
  const isTopThroughY = computed(
    () =>
      compRootElBounding.top.value < containerTop.value &&
      compRootElBounding.bottom.value > containerTop.value &&
      compRootElBounding.top.value < containerBottom.value &&
      compRootElBounding.bottom.value < containerBottom.value
  )
  // 容器滚动条在垂直方向上滚动时，组件是否在容器区域中完全显示
  const isShowedY = computed(
    () =>
      compRootElBounding.top.value >= containerTop.value &&
      compRootElBounding.bottom.value > containerTop.value &&
      compRootElBounding.top.value < containerBottom.value &&
      compRootElBounding.bottom.value <= containerBottom.value
  )
  // 容器滚动条在垂直方向上滚动时，组件是否在容器底部边界上
  const isBottomThroughY = computed(
    () =>
      compRootElBounding.top.value > containerTop.value &&
      compRootElBounding.bottom.value > containerTop.value &&
      compRootElBounding.top.value < containerBottom.value &&
      compRootElBounding.bottom.value > containerBottom.value
  )
  // 容器滚动条在垂直方向上滚动时，组件是否包含容器顶部和底部边界
  const isTopBottomThroughY = computed(
    () =>
      compRootElBounding.top.value < containerTop.value &&
      compRootElBounding.bottom.value > containerTop.value &&
      compRootElBounding.top.value < containerBottom.value &&
      compRootElBounding.bottom.value > containerBottom.value
  )

  // 容器滚动条在水平方向上滚动时，组件是否在容器左边界上
  const isLeftThroughX = computed(
    () =>
      compRootElBounding.left.value < containerLeft.value &&
      compRootElBounding.right.value > containerLeft.value &&
      compRootElBounding.left.value < containerRight.value &&
      compRootElBounding.right.value < containerRight.value
  )
  // 容器滚动条在水平方向上滚动时，组件是否在容器区域中完全显示
  const isShowedX = computed(
    () =>
      compRootElBounding.left.value >= containerLeft.value &&
      compRootElBounding.right.value > containerLeft.value &&
      compRootElBounding.left.value < containerRight.value &&
      compRootElBounding.right.value <= containerRight.value
  )
  // 容器滚动条在水平方向上滚动时，组件是否在容器右边界上
  const isRightThroughX = computed(
    () =>
      compRootElBounding.left.value > containerLeft.value &&
      compRootElBounding.right.value > containerLeft.value &&
      compRootElBounding.left.value < containerRight.value &&
      compRootElBounding.right.value > containerRight.value
  )
  // 容器滚动条在水平方向上滚动时，组件是否包含容器左边界和右边界
  const isLeftRightThroughX = computed(
    () =>
      compRootElBounding.left.value < containerLeft.value &&
      compRootElBounding.right.value > containerLeft.value &&
      compRootElBounding.left.value < containerRight.value &&
      compRootElBounding.right.value > containerRight.value
  )

  watchEffect(() => {
    // 容器没有滚动
    if (!containerScroll.isScrolling.value) {
      // 监听方向为垂直方向
      if (props.directionNotScroll === ScrollDirectionConst.Y) {
        if (isTopThroughY.value || isBottomThroughY.value || isTopBottomThroughY.value)
          beforeShowNotScrollHandler.value(compRootElBounding, containerElBounding)
        else if (isShowedY.value)
          showedNotScrollHandler.value(compRootElBounding, containerElBounding)
        else hiddenNotScrollHandler.value(compRootElBounding, containerElBounding)
      }
      // 监听方向为水平方向
      else {
        if (isLeftThroughX.value || isRightThroughX.value || isLeftRightThroughX.value)
          beforeShowNotScrollHandler.value(compRootElBounding, containerElBounding)
        else if (isShowedX.value)
          showedNotScrollHandler.value(compRootElBounding, containerElBounding)
        else hiddenNotScrollHandler.value(compRootElBounding, containerElBounding)
      }
      return
    }
    // 滚动条向上，元素向下
    if (containerScroll.directions.top) {
      // 开始出现
      if (isTopThroughY.value || isTopBottomThroughY.value)
        beforeShowHandler.value(compRootElBounding, containerElBounding)
      // 完全出现
      else if (isShowedY.value) showedHandler.value(compRootElBounding, containerElBounding)
      // 开始隐藏
      else if (isBottomThroughY.value)
        beforeHideHandler.value(compRootElBounding, containerElBounding)
      // 完全隐藏
      else hiddenHandler.value(compRootElBounding, containerElBounding)
    }
    // 滚动条向下，元素向上
    else if (containerScroll.directions.bottom) {
      // 开始出现
      if (isBottomThroughY.value || isTopBottomThroughY.value)
        beforeShowHandler.value(compRootElBounding, containerElBounding)
      // 完全出现
      else if (isShowedY.value) showedHandler.value(compRootElBounding, containerElBounding)
      // 开始隐藏
      else if (isTopThroughY.value) beforeHideHandler.value(compRootElBounding, containerElBounding)
      // 完全隐藏
      else hiddenHandler.value(compRootElBounding, containerElBounding)
    }
    // 滚动条向左，元素向右
    else if (containerScroll.directions.left) {
      // 开始出现
      if (isLeftThroughX.value || isLeftRightThroughX.value)
        beforeShowHandler.value(compRootElBounding, containerElBounding)
      // 完全出现
      else if (isShowedX.value) showedHandler.value(compRootElBounding, containerElBounding)
      // 开始隐藏
      else if (isRightThroughX.value)
        beforeHideHandler.value(compRootElBounding, containerElBounding)
      // 完全隐藏
      else hiddenHandler.value(compRootElBounding, containerElBounding)
    }
    // 滚动条向右，元素向左
    else if (containerScroll.directions.right) {
      // 开始出现
      if (isRightThroughX.value || isLeftRightThroughX.value)
        beforeShowHandler.value(compRootElBounding, containerElBounding)
      // 完全出现
      else if (isShowedX.value) showedHandler.value(compRootElBounding, containerElBounding)
      // 开始隐藏
      else if (isLeftThroughX.value)
        beforeHideHandler.value(compRootElBounding, containerElBounding)
      // 完全隐藏
      else hiddenHandler.value(compRootElBounding, containerElBounding)
    }
  })
</script>

<template>
  <div ref="compRootElRef" class="s-visibility-detail">
    <slot />
  </div>
</template>

<style lang="scss">
  @use './style' as *;
</style>
